package com.sayice.robotconflict.game;

import com.sayice.robotconflict.game.msg.Message;
import com.sayice.robotconflict.game.object.GameRoom;
import com.sayice.robotconflict.game.object.Player;
import com.sayice.robotconflict.game.object.Player.State;
import com.sayice.robotconflict.game.object.Team;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.*;
import java.util.stream.Stream;

/**
 * 游戏匹配器
 *
 * @author shubing
 */
public class GameMatcher {
    /**
     * 1v1匹配器
     */
    private static final GameMatcher ONE_MATCHER = new GameMatcher(State.MATCHING_1, 1, 0, 0);
    
    /**
     * 3v3匹配器
     */
    private static final GameMatcher THREE_MATCHER = new GameMatcher(State.MATCHING_3, 3, 2, 1);
    
    /**
     * 5v5匹配器
     */
    private static final GameMatcher FIVE_MATCHER = new GameMatcher(State.MATCHING_5, 5, 2, 1);
    
    /**
     * 匹配状态 (匹配队列中玩家的状态)
     */
    public final State matchState;
    
    /**
     * 队伍人数
     */
    private final int teamSize;
    
    /**
     * 匹配线程池
     */
    private final ThreadPoolExecutor matchExecutor;
    
    /**
     * 队伍匹配队列1
     */
    private final ConcurrentSkipListSet<Team> teamMatchSet1 = new ConcurrentSkipListSet<>();
    
    /**
     * 队伍匹配队列2
     */
    private final ConcurrentSkipListSet<Team> teamMatchSet2 = new ConcurrentSkipListSet<>();
    
    /**
     * 玩家匹配队列
     */
    private final ConcurrentSkipListSet<Player>[] playerMatchSets;
    
    /**
     * 不完整队伍匹配队列
     */
    private final ConcurrentSkipListSet<Team>[] partTeamMatchSets;
    
    /**
     * 玩家匹配队列下标
     */
    private int playerQueueIndex;
    
    /**
     * 不完整队伍匹配队列下标
     */
    private int partTeamQueueIndex;
    
    /**
     * 创建一个具有指定玩家匹配队列数量的 GameMatcher 对象
     *
     * @param matchState         匹配状态 (匹配队列中玩家的状态)
     * @param teamSize           队伍人数
     * @param playerQueueCount   玩家匹配队列数量
     * @param partTeamQueueCount 不完整队伍匹配队列数量
     */
    private GameMatcher(State matchState, int teamSize, int playerQueueCount, int partTeamQueueCount) {
        this.matchState = matchState;
        this.teamSize = teamSize;
        int totalCount = playerQueueCount + partTeamQueueCount + 2;
        this.matchExecutor = new ThreadPoolExecutor(totalCount, totalCount, 0, TimeUnit.MILLISECONDS,
                new SynchronousQueue<>(), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
        this.playerMatchSets = (ConcurrentSkipListSet<Player>[]) new ConcurrentSkipListSet<?>[playerQueueCount];
        this.partTeamMatchSets = (ConcurrentSkipListSet<Team>[]) new ConcurrentSkipListSet<?>[partTeamQueueCount];
        for (int i = 0; i < playerQueueCount; i++) {
            ConcurrentSkipListSet<Player> playerMatchSet = new ConcurrentSkipListSet<>();
            playerMatchSets[i] = playerMatchSet;
            matchExecutor.execute(() -> consumePlayerQueue(playerMatchSet));
        }
        for (int i = 0; i < partTeamQueueCount; i++) {
            ConcurrentSkipListSet<Team> partTeamMatchSet = new ConcurrentSkipListSet<>();
            partTeamMatchSets[i] = partTeamMatchSet;
            matchExecutor.execute(() -> consumePartTeamQueue(partTeamMatchSet));
        }
        matchExecutor.execute(() -> consumeTeamQueue(teamMatchSet1));
        matchExecutor.execute(() -> consumeTeamQueue(teamMatchSet2));
    }
    
    /**
     * 添加匹配
     *
     * @param team     队伍
     * @param teamSize 队伍人数
     * @return 添加成功为true，否则为false
     */
    public static boolean addMatch(Team team, int teamSize) {
        switch (teamSize) {
            case 1:
                return ONE_MATCHER.add(team);
            case 3:
                return THREE_MATCHER.add(team);
            case 5:
                return FIVE_MATCHER.add(team);
            default:
                throw new IllegalArgumentException("队伍人数不合法");
        }
    }
    
    /**
     * 添加匹配
     *
     * @param player   玩家
     * @param teamSize 队伍人数
     * @return 添加成功为true，否则为false
     */
    public static boolean addMatch(Player player, int teamSize) {
        switch (teamSize) {
            case 1:
                return ONE_MATCHER.add(player);
            case 3:
                return THREE_MATCHER.add(player);
            case 5:
                return FIVE_MATCHER.add(player);
            default:
                throw new IllegalArgumentException("队伍人数不合法");
        }
    }
    
    /**
     * 获取一个玩家匹配队列
     */
    private ConcurrentSkipListSet<Player> nextPlayerQueue() {
        ConcurrentSkipListSet<Player> playerMatchSet = playerMatchSets[playerQueueIndex];
        if (playerMatchSet.size() < 2) {
            return playerMatchSet;
        }
        playerQueueIndex = (playerQueueIndex + 1) % playerMatchSets.length;
        return playerMatchSet;
    }
    
    /**
     * 获取一个不完整队伍匹配队列
     */
    private ConcurrentSkipListSet<Team> nextPartTeamQueue() {
        ConcurrentSkipListSet<Team> partTeamMatchSet = partTeamMatchSets[partTeamQueueIndex];
        if (partTeamMatchSet.size() < 2) {
            return partTeamMatchSet;
        }
        partTeamQueueIndex = (partTeamQueueIndex + 1) % partTeamMatchSets.length;
        return partTeamMatchSet;
    }
    
    /**
     * 添加组队匹配
     *
     * @param team 队伍
     * @return 添加成功为true，否则为false
     */
    private boolean add(Team team) {
        int diff = teamSize - team.size();
        if (diff < 0) {
            throw new IllegalArgumentException("实际队伍人数越界");
        }
        if (!team.weakTryUpdatePlayersState(State.IDLE, matchState)) {
            team.send(new Message("action", "stopMatch", "队伍中有玩家状态异常"));
            return false;
        }
        boolean added = diff > 0 ? nextPartTeamQueue().add(team) : addToTeamQueue(team);
        if (added) {
            team.send(new Message("action", "enterMatch", null));
        }
        return added;
    }
    
    /**
     * 添加玩家匹配
     *
     * @param player 玩家
     * @return 添加成功为true，否则为false
     */
    private boolean add(Player player) {
        if (!player.state.compareAndSet(State.IDLE, matchState)) {
            return false;
        }
        // 1v1不需要经过 Player 队列
        boolean added = this == ONE_MATCHER ? addToTeamQueue(new Team(Collections.singletonMap(player.id, player)))
                : nextPlayerQueue().add(player);
        if (added) {
            player.send(new Message("action", "enterMatch", null));
        }
        return added;
    }
    
    /**
     * 添加队伍到队伍匹配队列
     *
     * @param team 队伍
     * @return 添加成功为true，否则为false
     */
    private boolean addToTeamQueue(Team team) {
        if (teamMatchSet1.size() < 2) {
            return teamMatchSet1.add(team);
        } else if (teamMatchSet2.size() < 2) {
            return teamMatchSet2.add(team);
        } else if (team.score < 50) {
            return teamMatchSet1.add(team);
        } else {
            return teamMatchSet2.add(team);
        }
    }
    
    /**
     * 消费队伍匹配队列
     *
     * @param teamMatchSet 队伍匹配队列
     */
    private void consumeTeamQueue(ConcurrentSkipListSet<Team> teamMatchSet) {
        // 取出 Team 创建对局
        while (true) {
            int size = teamMatchSet.size();
            if (size > 1) {
                Team team1 = teamMatchSet.pollFirst();
                Team team2 = teamMatchSet.pollFirst();
                teamMatchSet.add(team1);
                teamMatchSet.add(team2);
                // 若两个队伍都是全人机，则不创建，并移出匹配队列
                if (team1.isVirtual() && team2.isVirtual()) {
                    teamMatchSet.remove(team1);
                    teamMatchSet.remove(team2);
                    continue;
                }
                // 设置玩家状态
                final boolean[] existsCanceled = {false};
                if (this == ONE_MATCHER) {
                    // 若有玩家状态不正常(取消匹配)，则取消创建对局，将该玩家的 Team 移出匹配队列，状态正常的玩家继续匹配
                    // updated 用于保存状态正常的玩家，只需要保存一个
                    final Player[] updated = {null};
                    Stream.of(team1, team2).forEach(team -> {
                        Player player = team.players.values().stream().findFirst().get();
                        if (player.state.compareAndSet(matchState, State.GAMING)) {
                            updated[0] = player;
                        } else {
                            teamMatchSet.remove(team);
                            existsCanceled[0] = true;
                        }
                    });
                    if (existsCanceled[0]) {
                        if (updated[0] != null) {
                            // 状态正常的玩家回滚状态，继续匹配
                            updated[0].state.compareAndSet(State.GAMING, matchState);
                        }
                        continue;
                    }
                } else {
                    // 若有玩家状态不正常(取消匹配)，则取消创建对局，将该玩家所在 Team 移出匹配队列
                    // 所在 Team 的其他玩家重新组建 Team 加入到不完整队伍匹配队列，状态正常的 Team 继续匹配
                    // updated 用于保存状态正常的 Team，只需要保存一个
                    final Team[] updated = {null};
                    Stream.of(team1, team2).forEach(team -> {
                        Map<String, Player> normalPlayers = new HashMap<>(8);
                        if (team.tryUpdatePlayersState(matchState, State.GAMING,
                                player -> normalPlayers.put(player.id, player))) {
                            updated[0] = team;
                        } else {
                            teamMatchSet.remove(team);
                            existsCanceled[0] = true;
                            // 状态正常的玩家组建新的 Team 加入到不完整队伍匹配队列
                            if (!normalPlayers.isEmpty()) {
                                nextPartTeamQueue().add(new Team(normalPlayers));
                            }
                        }
                    });
                    if (existsCanceled[0]) {
                        if (updated[0] != null) {
                            // 状态正常的 Team 回滚状态，继续匹配
                            updated[0].updateExpectedPlayersState(State.GAMING, matchState);
                        }
                        continue;
                    }
                }
                if (GameRoom.createGame(team1, team2)) {
                    teamMatchSet.remove(team1);
                    teamMatchSet.remove(team2);
                } else {
                    team1.updateExpectedPlayersState(State.GAMING, matchState);
                    team2.updateExpectedPlayersState(State.GAMING, matchState);
                }
            } else if (size > 0) {
                add(Player.newVirtual());
            } else {
                try {
                    TimeUnit.MILLISECONDS.sleep(1);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }
    
    /**
     * 消费玩家匹配队列
     *
     * @param playerMatchSet 玩家匹配队列
     */
    private void consumePlayerQueue(ConcurrentSkipListSet<Player> playerMatchSet) {
        // 取出玩家创建 Team 加入到队伍匹配队列
        while (true) {
            int size = playerMatchSet.size();
            if (size >= teamSize) {
                Map<String, Player> players = new HashMap<>((int) Math.ceil(teamSize / 0.75));
                for (int i = 0; i < teamSize; i++) {
                    Player player = playerMatchSet.pollFirst();
                    players.put(player.id, player);
                }
                addToTeamQueue(new Team(players));
            } else if (size > 0) {
                add(Player.newVirtual());
            } else {
                try {
                    TimeUnit.MILLISECONDS.sleep(1);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }
    
    /**
     * 消费不完整队伍匹配队列
     *
     * @param partTeamMatchSet 不完整队伍匹配队列
     */
    private void consumePartTeamQueue(ConcurrentSkipListSet<Team> partTeamMatchSet) {
        // 取出不完整的 Team 补全后加入到队伍匹配队列
        while (true) {
            if (partTeamMatchSet.size() > 0) {
                Team team = partTeamMatchSet.pollFirst();
                Map<String, Player> players = team.players;
                int diff = teamSize - team.size();
                for (int i = 0; i < diff; i++) {
                    Player player;
                    while (true) {
                        if ((player = nextPlayerQueue().pollFirst()) != null) {
                            players.put(player.id, player);
                            break;
                        } else {
                            add(Player.newVirtual());
                        }
                    }
                }
                addToTeamQueue(new Team(players));
            } else {
                try {
                    TimeUnit.MILLISECONDS.sleep(1);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }
}
